﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using D20.Magic.Enchantments;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.ConstrainedExecution;
using System.Globalization;

namespace D20.Entities
{
    /// <summary>
    /// A generic entity. Any interactable object is an entity.
    /// </summary>
    public abstract class Entity
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Entity"/> class.
        /// </summary>
        protected Entity()
        {
            Identity = new Identity();
            PreviousHealth = 1;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="Entity"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        protected Entity(string name)
        {
            Identity = new Identity(name);
        }
        /// <summary>
        /// Gets the identity.
        /// </summary>
        /// <value>The identity.</value>
        public Identity Identity { get; private set; }
        private List<Enchantment> enchantments = new List<Enchantment>();
        /// <summary>
        /// Gets the enchantments active on the character.
        /// </summary>
        /// <value>The enchantments.</value>
        [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
        public List<Enchantment> Enchantments
        {
            get { return enchantments; }
        }

        /// <summary>
        /// Gets or sets the owner of the entity.
        /// </summary>
        /// <value>The owner. If null, there is no owner.</value>
        public Entity Owner { get; set; }

        //initialised to 1 so we're not automatically dead.
        private float health = 1;
        /// <summary>
        /// Gets the health of the entity. If this reaches 0, then the entity dies.
        /// </summary>
        /// <value>The health.</value>
        public float Health
        {
            get { return health; }
            set { if (value != health)ApplyHealthModification(null, value - health); }
        }
        private float maxHealth = 1;
        /// <summary>
        /// Gets or sets the max health.
        /// </summary>
        /// <value>The max health.</value>
        public float MaxHealth
        {
            get { return maxHealth; }
            set
            {
                Debug.Assert(value > 0, "value > 0");
                if (value <= 0)
                {
                    throw new ArgumentOutOfRangeException("value", "Max health must be positive.");
                }
                maxHealth = value;
                if (health > maxHealth)
                {
                    health = maxHealth;
                }
            }
        }
        //A list of previous deaths, for bookkeeping.
        private Stack<DeathEventArgs> previousDeaths = new Stack<DeathEventArgs>(1);

        /// <summary>
        /// Revives the entity.
        /// </summary>
        /// <param name="cause">The cause.</param>
        public void Revive(Entity cause)
        {
            Revive(cause, MaxHealth);
        }

        /// <summary>
        /// Revives the entity.
        /// </summary>
        /// <param name="cause">The cause.</param>
        /// <param name="newHealth">The new health.</param>
        public void Revive(Entity cause, float newHealth)
        {
            Debug.Assert(health <= 0, "health <= 0", "The entity isn't dead.");
            if (health > 0)
            {
                throw new InvalidOperationException("The entity is not dead.");
            }
            //check for erronous new health
            Debug.Assert(newHealth > 0, "newHealth > 0", String.Format(CultureInfo.CurrentCulture, "Entity is being revived to negative/zero health. New health: {0}", newHealth));
            if (newHealth <= 0)
            {
                throw new ArgumentOutOfRangeException("newHealth", String.Format(CultureInfo.CurrentCulture, "Entity is being revived to negative/zero health. New health: {0}", newHealth));
            }

            Debug.Assert(newHealth <= MaxHealth, "newHealth <= MaxHealth");
            if (newHealth > MaxHealth)
            {
                throw new ArgumentOutOfRangeException("newHealth", "Entity is being revived to more than maximum health.");
            }

            this.health = newHealth;
            OnReviveEventHandler handler = OnRevive;
            if (handler != null)
            {
                handler(new ReviveEventArgs(cause, newHealth, this));
            }

        }
        //for when we break something.
        /// <summary>
        /// Gets or sets the previous health.
        /// </summary>
        /// <value>The previous health.</value>
        protected float PreviousHealth { get; set; }
        /// <summary>
        /// Applies a health modification. If positive, then it will increase health, otherwise, it will decrease it.
        /// </summary>
        /// <param name="cause">The cause of the modification.</param>
        /// <param name="additiveModification">The additive modification. If positive, then it will increase health, otherwise, it will decrease it.</param>
        /// <exception cref="D20.Entities.AlreadyDeadException">Thrown when the object is already dead.</exception>
        public void ApplyHealthModification(Entity cause, float additiveModification)
        {
            //just a sanity check.
            Debug.Assert(health > 0, "health > 0", "Entity's already dead.");
            if (health <= 0)
            {
                throw new AlreadyDeadException(previousDeaths.Peek());
            }

            //negative health, so they want to apply damage.
            // Note that this will mostly be called in battle, so we check for damage first.
            if (additiveModification < 0)
            {
                OnDamageEventHandler handler = OnDamage;
                if (handler != null)
                {
                    handler(new DamageEventArgs(cause, additiveModification, this));
                }
            }

            // positive health, so they want to heal
            else if (additiveModification > 0)
            {
                //we can't be exceeding maximum health.
                Debug.Assert(health + additiveModification <= MaxHealth, "health + additiveModification <= MaxHealth");
                if (health + additiveModification > MaxHealth)
                {
                    throw new ArgumentOutOfRangeException("additiveModification", "Entity is being revived to more than maximum health.");
                }

                OnHealEventHandler handler = OnHeal;
                if (handler != null)
                {
                    handler(new HealEventArgs(cause, additiveModification, this));
                }
            }
            else
            {
                // additive = 0, so throw an exception.
                throw new ArgumentOutOfRangeException("additiveModification", "Applied health modifier cannot be 0.");
            }

            PreviousHealth = health;
            //apply the health
            health += additiveModification;

            //check to see if we have died, and call all registered handlers.
            if (health <= 0)
            {
                DeathEventArgs args = new DeathEventArgs(cause, additiveModification, this);
                previousDeaths.Push(args); // to keep track of how we died.
                OnDeathEventHandler handler = OnDeath;
                if (handler != null)
                {
                    handler(args);
                }
            }

        }
        #region EventHandlers
        /// <summary>
        /// Occurs when the entity dies.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
        public event OnDeathEventHandler OnDeath;

        /// <summary>
        /// Occurs when the entity dies.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
        public event OnReviveEventHandler OnRevive;

        /// <summary>
        /// Occurs when the entity takes damage.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
        public event OnDamageEventHandler OnDamage;

        /// <summary>
        /// Occurs when the entity is healed.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly")]
        public event OnHealEventHandler OnHeal;
        #endregion
    }

    #region Health mod/death events

    #region Death
    /// <summary>
    /// Event args for when an <see cref="D20.Entities.Entity"/> dies.
    /// </summary>
    public class DeathEventArgs : EventArgs
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DeathEventArgs"/> class.
        /// </summary>
        /// <param name="cause">The cause of death.</param>
        /// <param name="damage">The damage.</param>
        /// <param name="target">The target.</param>
        public DeathEventArgs(Entity cause, float damage, Entity target)
        {
            Cause = cause;
            Damage = damage;
            Target = target;
        }
        /// <summary>
        /// Gets the cause of death.
        /// </summary>
        /// <value>The cause of death.</value>
        public Entity Cause { get; private set; }
        /// <summary>
        /// Gets the damage.
        /// </summary>
        /// <value>The damage.</value>
        public float Damage { get; private set; }
        /// <summary>
        /// Gets the target.
        /// </summary>
        /// <value>The target.</value>
        public Entity Target { get; private set; }
    }
    /// <summary>
    /// A delegate that is called when an entity dies.
    /// </summary>
    public delegate void OnDeathEventHandler(DeathEventArgs e);
    #endregion

    #region Damage
    /// <summary>
    /// Event arguments for the <see cref="OnDamageEventHandler"/> delegate.
    /// </summary>
    public class DamageEventArgs : EventArgs
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DamageEventArgs"/> class.
        /// </summary>
        /// <param name="cause">The cause.</param>
        /// <param name="damage">The damage.</param>
        /// <param name="target">The target.</param>
        public DamageEventArgs(Entity cause, float damage, Entity target)
        {
            Cause = cause;
            Damage = damage;
            Target = target;
        }
        /// <summary>
        /// Gets the cause.
        /// </summary>
        /// <value>The cause.</value>
        public Entity Cause { get; private set; }
        /// <summary>
        /// Gets the damage.
        /// </summary>
        /// <value>The damage.</value>
        public float Damage { get; private set; }
        /// <summary>
        /// Gets the target.
        /// </summary>
        /// <value>The target.</value>
        public Entity Target { get; private set; }
    }
    /// <summary>
    /// A delegate that is called when an entity is damaged.
    /// </summary>
    public delegate void OnDamageEventHandler(DamageEventArgs e);
    #endregion

    #region Heal
    /// <summary>
    /// Event arguments for the <see cref="OnHealEventHandler"/> delegate.
    /// </summary>
    public class HealEventArgs : EventArgs
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="HealEventArgs"/> class.
        /// </summary>
        /// <param name="cause">The cause.</param>
        /// <param name="addedHealth">The added health.</param>
        /// <param name="target">The target.</param>
        public HealEventArgs(Entity cause, float addedHealth, Entity target)
        {
            Cause = cause;
            AddedHealth = addedHealth;
            Target = target;
        }
        /// <summary>
        /// Gets the cause.
        /// </summary>
        /// <value>The cause.</value>
        public Entity Cause { get; private set; }
        /// <summary>
        /// Gets the added health.
        /// </summary>
        /// <value>The added health.</value>
        public float AddedHealth { get; private set; }
        /// <summary>
        /// Gets the target.
        /// </summary>
        /// <value>The target.</value>
        public Entity Target { get; private set; }
    }
    /// <summary>
    /// A delegate that is called when an entity is healed.
    /// </summary>
    public delegate void OnHealEventHandler(HealEventArgs e);
    #endregion

    #region Revive
    /// <summary>
    /// Event arguments for the <see cref="OnReviveEventHandler"/> delegate.
    /// </summary>
    public class ReviveEventArgs : EventArgs
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ReviveEventArgs"/> class.
        /// </summary>
        /// <param name="cause">The cause.</param>
        /// <param name="newHealth">The new health.</param>
        /// <param name="target">The target.</param>
        public ReviveEventArgs(Entity cause, float newHealth, Entity target)
        {
            Cause = cause;
            NewHealth = newHealth;
            Target = target;
        }
        /// <summary>
        /// Gets the cause.
        /// </summary>
        /// <value>The cause.</value>
        public Entity Cause { get; private set; }
        /// <summary>
        /// Gets the new health.
        /// </summary>
        /// <value>The new health.</value>
        public float NewHealth { get; private set; }
        /// <summary>
        /// Gets the target.
        /// </summary>
        /// <value>The target.</value>
        public Entity Target { get; private set; }
    }
    /// <summary>
    /// A delegate that is called when an entity is revived.
    /// </summary>
    public delegate void OnReviveEventHandler(ReviveEventArgs e);
    #endregion

    #region AlreadyDeadException

    /// <summary>
    /// Signifies than a <see cref="D20.Entities.Entity"/> is already dead.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")]
    [Serializable]
    public class AlreadyDeadException : Exception
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AlreadyDeadException"/> class.
        /// </summary>
        public AlreadyDeadException()
            : base("This entity has already died.")
        {

        }
        /// <summary>
        /// Initializes a new instance of the <see cref="AlreadyDeadException"/> class.
        /// </summary>
        /// <param name="message">The message.</param>
        public AlreadyDeadException(string message)
            : base(message)
        {

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AlreadyDeadException"/> class.
        /// </summary>
        /// <param name="circumstances">The <see cref="D20.Entities.DeathEventArgs"/> instance containing the event data.</param>
        public AlreadyDeadException(DeathEventArgs circumstances)
            : this()
        {
            DeathCircumstances = circumstances;
        }
        /// <summary>
        /// Gets the death circumstances.
        /// </summary>
        /// <value>The death circumstances.</value>
        public DeathEventArgs DeathCircumstances { get; private set; }
    }
    #endregion

    #endregion
}
